package com.wwh.wwhspringmvc.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wwh.wwhspringmvc.Handler.WwhHandler;
import com.wwh.wwhspringmvc.annotation.Controller;
import com.wwh.wwhspringmvc.annotation.RequestMapping;
import com.wwh.wwhspringmvc.annotation.RequestParam;
import com.wwh.wwhspringmvc.annotation.ResponseBody;
import com.wwh.wwhspringmvc.context.WwhWebApplicationContext;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Albert
 * @date 2024/9/5-15:49
 * @describe
 */
public class WwhDispatcherServlet extends HttpServlet {
    private List<WwhHandler> handlerList = new ArrayList<>();
    private WwhWebApplicationContext wwhWebApplicationContext = null;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost...");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet...");
        //System.out.println("工程路径 = " + getServletContext().getContextPath());
        executeDispatch(req, resp);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //这个问题很奇怪，也是第一次遇到，因为以前在servlet的doGet/doPost方法中要获取ServletContext对象时都是这样写的，也没有出现过java.lang.NullPointerException(空指针)异常，上网查了一下出现这个异常的原因：原来是我重写了init(ServletConfig)方法，但重写的init方法内部没有调用super.init(config);就是这导致了错误！父类的 init(ServletConfig)有处理获取ServletContext对象的引用，在doGet/doPost/service方法方法中才能够通过 getServletContext()方法获取到SeverletContext对象！重写了Servlet的init方法后一定要记得调用父类的init方法，否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时就会出现java.lang.NullPointerException异常
        super.init(config);//一定要记得调用，否则getServletContext()将发生空指针异常
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        wwhWebApplicationContext = new WwhWebApplicationContext(contextConfigLocation);
        wwhWebApplicationContext.init();
        initHandLerMapping();
        System.out.println("handlerList = " + handlerList);
    }

    /**
     * 映射url和处理器
     */
    private void initHandLerMapping() {
        if (wwhWebApplicationContext.ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : wwhWebApplicationContext.ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(Controller.class)) {
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method method : declaredMethods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        String url = requestMapping.value();
                        WwhHandler wwhHandler = new WwhHandler(url, entry.getValue(), method);
                        handlerList.add(wwhHandler);
                    }
                }
            }
        }
    }

    //通过request对象获取对应的handler
    private WwhHandler getHandler(HttpServletRequest req) {
        String requestURI = req.getRequestURI();
        for (WwhHandler wwhHandler : handlerList) {
            if (requestURI.equals(getServletContext().getContextPath() + wwhHandler.getUrl())) {
                return wwhHandler;
            }
        }
        return null;
    }

    //分发请求
    private void executeDispatch(HttpServletRequest req, HttpServletResponse resp) {
        WwhHandler handlerMap = getHandler(req);

        try {
            if (handlerMap != null) {
                //1.得到目标方法的参数信息
                Class<?>[] parameterTypes = handlerMap.getMethod().getParameterTypes();
                //2.创建一个参数数组，对应实参数组，后面调用目标方法时使用
                Object[] params = new Object[parameterTypes.length];
                //3.遍历形参数组
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    //如果形参是HttpServletRequest，就填充到params
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = req;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = resp;
                    }
                }

                // 下面的方法只能适配固定的参数
                //Object handler = handlerMap.getHandler();
                //Method method = handlerMap.getMethod();
                //method.invoke(handler, req, resp);

                //将Http参数封装到参数数组里面
                //1.获取Http参数集合,前一个参数是参数名，后者是参数值的数组
                req.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = req.getParameterMap();
                //2.按照顺序填充到参数数组
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String name = entry.getKey();
                    String value = entry.getValue()[0];//只考虑提交的参数是单值的
                    int indexRequestParameter = getIndexRequestParameterIndex(handlerMap.getMethod(), name);
                    if (indexRequestParameter != -1) {
                        params[indexRequestParameter] = value;
                    } else {//如果没有找到,默认机制处理
                        //1.得到目标方法所有形参的名称
                        List<String> paramsNames = getParamsName(handlerMap.getMethod());
                        for (int i = 0; i < paramsNames.size(); i++) {
                            if (name.equals(paramsNames.get(i))) {
                                params[i] = value;
                                break;
                            }
                        }
                    }
                }

                Object result = handlerMap.getMethod().invoke(handlerMap.getHandler(), params);

                if (result instanceof String) {
                    if (((String) result).contains(":")) {
                        String[] split = ((String) result).split(":");
                        String viewType = split[0];//forward/redirect
                        String viewPage = split[1];

                        if ("forward".equals(viewType)) {
                            req.getRequestDispatcher(viewPage).forward(req, resp);
                        } else if ("redirect".equals(viewType)) {
                            resp.sendRedirect(getServletContext().getContextPath() + viewPage);
                        }
                    } else {//默认请求转发
                        req.getRequestDispatcher((String) result).forward(req, resp);
                    }
                } else if (result instanceof List) {
                    Method method = handlerMap.getMethod();
                    if(method.isAnnotationPresent(ResponseBody.class)){
                        ObjectMapper objectMapper = new ObjectMapper();
                        String resultJson = objectMapper.writeValueAsString(result);
                        resp.setContentType("text/html;charset=utf-8");
                        PrintWriter writer = resp.getWriter();
                        writer.write(resultJson);
                        writer.flush();
                        writer.close();
                    }
                }

            } else {
                resp.getWriter().write("<h1>404 NOT FOUND<h1>");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //专门写一个方法得到请求参数对应的参数下标，即目标方法的第几个形参
    private int getIndexRequestParameterIndex(Method method, String name) {
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            //判断是不是有注解RequestParam
            if (parameter.isAnnotationPresent(RequestParam.class)) {
                RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
                String paramName = requestParam.value();
                if (name.equals(paramName)) {
                    return i;
                }
            }

        }
        //如果没有匹配成功，返回-1
        return -1;
    }

    //获取所给所有形参的名称
    private List<String> getParamsName(Method method) {
        List<String> names = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        //这样获取的参数名是[arg1, arg2, arg3...]，因此需要使用Java 8 的特性来解决
        for (Parameter parameter : parameters) {
            names.add(parameter.getName());
        }
        //System.out.println(names);
        return names;
    }
}
